﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using System.IO;

namespace Prospectus
{
    public class Player : AnimatedSprite
    {
        float itemStrength = 20.0f;
        public float itemSpeed = 10.0f / 60.0f;
        public int totalTime = 0;
        public float deltaTime = 0.0f;

        public int selectedItem = 0;
        public List<Block> Inventory;

        private const float speed = 8000.0f;
        private const float maxSpeed = 1750.0f;
        private const float groundDrag = 0.48f;
        private const float airDrag = 0.58f;

        float movement;
        float previousBottom;

        private const float maxJumpTime = 0.40f;
        private const float jumpLaunchVelocity = -3000.0f;
        private const float gravity = 1850.0f;
        private const float maxFallSpeed = 400.0f;
        private const float jumpControlPower = 0.07f;

        private bool isJumping;
        private bool wasJumping;
        private float jumpTime;
        bool isOnSolidGround;

        public bool gameWon { get; set; }
        
        public TileLayer TileLayer
        {
            get { return tileLayer; }
        }
        TileLayer tileLayer;

        //public Texture2D Texture
        //{
        //    get { return texture; }
        //}
        //Texture2D texture;

        //public Vector2 Position
        //{
        //    get { return position; }
        //    set { position = value; }
        //}
        //Vector2 position;

        public float Speed
        {
            get { return speed; }
        }

        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }
        Vector2 velocity;

        public bool IsJumping
        {
            get { return isJumping; }
            set { isJumping = value; }
        }

        public bool IsOnSolidGround
        {
            get { return isOnSolidGround; }
            set { isOnSolidGround = value; }
        }

        //private Rectangle localBounds;

        //public Rectangle BoundingRect
        //{
        //    get
        //    {
        //        int left = (int)Math.Round(Position.X) + localBounds.X;
        //        int top = (int)Math.Round(Position.Y) + localBounds.Y;

        //        return new Rectangle(left, top, localBounds.Width, localBounds.Height);
        //    }
        //}

        public Point CurrentCell
        {
            get { return Engine.ConvertVectorToCell(Position); }
        }
                
        //public Inventory Inventory
        //{
        //    get { return inventory; }
        //    private set { inventory = value; }
        //}
        //private Inventory inventory;

        //public Block Inventory;

        public Player(TileLayer tileLayer, Vector2 position)
        {
            this.tileLayer = tileLayer;
            gameWon = false;

            LoadContent();

            Reset(position);
        }

        public void LoadContent()
        {
            Inventory = new List<Block>();

            this.texture = TileLayer.Content.Load<Texture2D>("Sprites/playerSheet");
            this.OriginOffset = new Vector2(32, 64);

            //int width = (int)(texture.Width * .95);
            //int left = (texture.Width - width) / 2;
            //int height = (int)(texture.Height);
            //int top = texture.Height - height;
            //localBounds = new Rectangle(left, top, width, height);

            //Inventory = new Inventory();

            FrameAnimation idle = new FrameAnimation(2, 64, 64, 0, 0);
            idle.FramesPerSecond = 1;
            Animations.Add("Idle", idle);

            FrameAnimation walk = new FrameAnimation(2, 64, 64, 128, 0);
            walk.FramesPerSecond = 4;
            Animations.Add("Walk", walk);

            FrameAnimation jump = new FrameAnimation(1, 64, 64, 0, 64);
            jump.FramesPerSecond = 4;
            Animations.Add("Jump", jump);

            CurrentAnimationName = "Idle";
        }

        private void Reset(Vector2 position)
        {
            Position = position;
            Velocity = Vector2.Zero;
        }

        public void Update(GameTime gameTime, Camera camera)
        {
            HandleInput(camera, gameTime);

            ApplyPhysics(gameTime);
            
            movement = 0.0f;
            isJumping = false;

            base.Update(gameTime);
        }

        private void ApplyPhysics(GameTime gameTime)
        {
            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector2 previousPosition = Position;

            // Base velocity is a combination of horizontal movement control and
            // accelaration downward due to gravity.
            velocity.X += movement * speed * elapsedTime;
            velocity.Y = MathHelper.Clamp(velocity.Y + gravity * elapsedTime, -maxFallSpeed, maxFallSpeed);

            velocity.Y = DoJump(velocity.Y, gameTime);

            // Apply pseudo-drag horizontally.
            if (IsOnSolidGround)
                velocity.X *= groundDrag;
            else
            {
                velocity.X *= airDrag;
                CurrentAnimationName = "Jump";
            }

            // Prevent the player from running faster than his top speed.
            velocity.X = MathHelper.Clamp(velocity.X, -maxSpeed, maxSpeed);

            Position += velocity * elapsedTime;
            Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));
            
            // Handle Collisions
            HandleCollisions();

            if (Position.X == previousPosition.X)
                velocity.X = 0;

            if (Position.Y == previousPosition.Y)
                velocity.Y = 0;

            //if (Position == previousPosition)
            //    CurrentAnimationName = "Idle";
        }

        private float DoJump(float velocityY, GameTime gameTime)
        {
            if (isJumping)
            {
                CurrentAnimationName = "Jump";
                // Begin or continue a jump
                if ((!wasJumping && IsOnSolidGround) || jumpTime > 0.0f)
                {
                    jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                }

                // If we are in the ascent of the jump
                if (0.0f < jumpTime && jumpTime <= maxJumpTime)
                {
                    // Fully override the vertical velocity with a power curve that gives the player more control over the top of the jump
                    velocityY = jumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / maxJumpTime, jumpControlPower));
                }
                else
                {
                    // Reached the apex of the jump
                    jumpTime = 0.0f;
                }
            }
            else
            {
                // Continues not jumping or cancels a jump in progress
                jumpTime = 0.0f;
            }
            wasJumping = isJumping;

            return velocityY;
        }

        private void HandleCollisions()
        {
            // Get the player's bounding rectangle and find neighboring tiles.
            Rectangle bounds = Bounds;           

            int leftBlock = (int)Math.Floor((float)bounds.Left / Engine.BlockWidth);
            int rightBlock = (int)Math.Ceiling(((float)bounds.Right / Engine.BlockWidth)) - 1;
            int topBlock = (int)Math.Floor((float)bounds.Top / Engine.BlockHeight);
            int bottomBlock = (int)Math.Ceiling(((float)bounds.Bottom / Engine.BlockHeight)) - 1;

            // Reset flag to search for ground collision.
            isOnSolidGround = false;

            // For each potentially colliding tile,
            for (int y = topBlock; y <= bottomBlock; ++y)
            {
                for (int x = leftBlock; x <= rightBlock; ++x)
                {
                    // If this tile is collidable,
                    BlockType collision = TileLayer.GetBlockType(x, y);
                    if (collision != BlockType.Passable)
                    {
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle blockBounds = TileLayer.GetBlockBounds(x, y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, blockBounds);
                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY < absDepthX || collision == BlockType.Platform)
                            {
                                if (collision == BlockType.Finish)
                                    gameWon = true;

                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= blockBounds.Top)
                                    isOnSolidGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == BlockType.Impassable || isOnSolidGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Position = new Vector2(Position.X, Position.Y + depth.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = Bounds;
                                }
                            }
                            else if (collision == BlockType.Impassable) // Ignore platforms.
                            {
                                // Resolve the collision along the X axis.
                                Position = new Vector2(Position.X + depth.X, Position.Y);

                                // Perform further collisions with the new bounds.
                                bounds = Bounds;
                            }
                        }
                    }
                }
            }

            // Save the new bounds bottom.
            previousBottom = bounds.Bottom;
        }

        private void HandleInput(Camera camera, GameTime gameTime)
        {
            if (movement == 0.0f)
            {
                CurrentAnimationName = "Idle";
                //SpriteEffects = SpriteEffects;
            }

            if (InputManager.IsKeyDown(Keys.A)) 
            { 
                movement = -1.0f;
                CurrentAnimationName = "Walk";
                SpriteEffects = SpriteEffects.FlipHorizontally;
            }
            else if (InputManager.IsKeyDown(Keys.D)) 
            { 
                movement = 1.0f;
                CurrentAnimationName = "Walk";
                SpriteEffects = SpriteEffects.None;
            }

            isJumping = (InputManager.IsKeyDown(Keys.Space));

            // NEED TO FIX BUG THAT DOESN'T RESET TOTALTIME WHEN CHANGING TO NEW BLOCK MID-DIG \\
            if (InputManager.IsMouseLeftDown())
            {
                Point previousMouseCell = Point.Zero;
                Point mouseCell = Engine.ConvertVectorToCell(
                    new Vector2(camera.Position.X + InputManager.MousePosition.X,
                                camera.Position.Y + InputManager.MousePosition.Y));

                if (mouseCell != previousMouseCell &&
                        TileLayer.GetBlock(mouseCell.X, mouseCell.Y).Texture != null)
                {
                    totalTime += gameTime.ElapsedGameTime.Milliseconds;
                }

                if ((float)totalTime / 1000 >= itemSpeed)
                {
                    if (mouseCell != previousMouseCell &&
                        TileLayer.GetBlock(mouseCell.X, mouseCell.Y).Texture != null)
                    {
                        MineBlock(/*Current Item Selected in Inventory,*/TileLayer.GetBlock(mouseCell.X, mouseCell.Y), mouseCell.X, mouseCell.Y);
                        //Inventory.AddItem(TileLayer.GetBlock(mouseCell.X, mouseCell.Y));
                    }
                    deltaTime = (float)totalTime / 1000;
                    totalTime = 0;
                }

                int topBlock = mouseCell.Y - 1;
                int rightBlock = mouseCell.X + 1;
                int bottomBlock = mouseCell.Y + 1;
                int leftBlock = mouseCell.X - 1;

                // For each potentially colliding tile,
                for (int y = topBlock; y <= bottomBlock; ++y)
                {
                    for (int x = leftBlock; x <= rightBlock; ++x)
                    {
                        TileLayer.UpdateBlock(x, y);
                    }
                }

                previousMouseCell = mouseCell;
            }
            else
            {
                totalTime = 0;
            }

            if (InputManager.IsMouseRightDown())
            {
                Point previousMouseCell = Point.Zero;
                Point mouseCell = Engine.ConvertVectorToCell(
                    new Vector2(camera.Position.X + InputManager.MousePosition.X,
                                camera.Position.Y + InputManager.MousePosition.Y));
                Point mouseCellConverted = Engine.ConvertCellToPoint(mouseCell);
                if (mouseCell != previousMouseCell && Inventory.Count > 0 && !Bounds.Contains(mouseCellConverted))
                {
                    TileLayer.PlaceBlock(
                        mouseCell.X,
                        mouseCell.Y,
                        Inventory[selectedItem]);
                    //Inventory.RemoveItem(Inventory.Items[Inventory.Capacity - 1]);
                }

                int topBlock = mouseCell.Y - 1;
                int rightBlock = mouseCell.X + 1;
                int bottomBlock = mouseCell.Y + 1;
                int leftBlock = mouseCell.X - 1;

                // For each potentially colliding tile,
                for (int y = topBlock; y <= bottomBlock; ++y)
                {
                    for (int x = leftBlock; x <= rightBlock; ++x)
                    {
                        TileLayer.UpdateBlock(x, y);
                    }
                }

                previousMouseCell = mouseCell;
            }

            if (InputManager.IsMouseWheelUp() && Inventory.Count > 0)
            {
                selectedItem--;

                if (selectedItem < 0)
                    selectedItem = Inventory.Count - 1;
            }

            if (InputManager.IsMouseWheelDown() && Inventory.Count > 0)
            {
                selectedItem++;

                if (selectedItem >= Inventory.Count)
                    selectedItem = 0;
            }
            
            deltaTime = gameTime.ElapsedGameTime.Milliseconds;
        }

        private void MineBlock(Block block, int x, int y)
        {
            bool hasBlock = false;
            // Simple test to see if player's Item 'mining strength' idea works
            //if (/*Item's mining power / strength*/ itemStrength > block.Strength)
            //{
            //    Inventory = TileLayer.blocksDictionary[block.Name + "SingleTop"];
            //    TileLayer.RemoveBlock(x, y);
            //}
            block.Strength = block.Strength - itemStrength;
            if (block.Strength <= 0)
            {
                if (Inventory.Count == 0)
                {
                    Inventory.Add(TileLayer.blocksDictionary[block.Name + "SingleTop"]);
                }
                else
                {
                    for (int i = 0; i < Inventory.Count; i++)
                    {
                        if (Inventory[i].Name == block.Name)
                        {
                            hasBlock = true;
                            break;
                        }                        
                    }
                    if (!hasBlock)
                        Inventory.Add(TileLayer.blocksDictionary[block.Name + "SingleTop"]);
                }

                
                TileLayer.RemoveBlock(x, y);
            }
        }

        //public void Draw(SpriteBatch spriteBatch)
        //{            
        //    spriteBatch.Draw(texture, position, Color.White);
        //}
    }
}